#include "webserver.h"

using namespace std;

WebServer::WebServer(
            int port, int trigMode, int timeoutMS, bool OptLinger,
            int sqlPort, const char* sqlUser, const  char* sqlPwd,
            const char* dbName, int connPoolNum, int threadNum,
            bool openLog, int logLevel, int logQueSize):
            port_(port), openLinger_(OptLinger), timeoutMS_(timeoutMS), isClose_(false),
            timer_(new HeapTimer()), threadpool_(new ThreadPool(threadNum)), epoller_(new Epoller())
    {
    srcDir_ = getcwd(nullptr, 256); // /home/rylan/文档/HTTPServer
    assert(srcDir_); 
    strncat(srcDir_, "/resources/", 14); // home/rylan/文档/HTTPServer/resources/      
    HttpConn::userCount = 0;
    HttpConn::srcDir = srcDir_;
    SqlConnPool::Instance()->Init("localhost", sqlPort, sqlUser, sqlPwd, dbName, connPoolNum); //创建数据库连接池实例并初始化
    InitEventMode_(trigMode);//初始化事件模式
    if(!InitSocket_()) { isClose_ = true;} //初始化socket，启动监听，并用epoll检测监听文件描述符，设置监听文件描述符非阻塞，失败则关闭连接
    //初始化日志系统
    if(openLog) {
        Log::Instance()->init(logLevel, "./log", ".log", logQueSize); 
        if(isClose_) { LOG_ERROR("========== Server init error!=========="); }
        else {
            LOG_INFO("========== Server init ==========");
            LOG_INFO("Port:%d, OpenLinger: %s", port_, OptLinger? "true":"false");
            LOG_INFO("Listen Mode: %s, OpenConn Mode: %s",
                            (listenEvent_ & EPOLLET ? "ET": "LT"),
                            (connEvent_ & EPOLLET ? "ET": "LT"));
            LOG_INFO("LogSys level: %d", logLevel);
            LOG_INFO("srcDir: %s", HttpConn::srcDir);
            LOG_INFO("SqlConnPool num: %d, ThreadPool num: %d", connPoolNum, threadNum);
        }
    }
}

WebServer::~WebServer() {
    close(listenFd_);
    isClose_ = true;
    free(srcDir_);
    SqlConnPool::Instance()->ClosePool();
}
//设置监听文件描述符和通讯文件描述符的epoll工作模式
void WebServer::InitEventMode_(int trigMode) {
    listenEvent_ = EPOLLRDHUP; //检测监听的文件描述符是否关闭socket
    connEvent_ = EPOLLONESHOT | EPOLLRDHUP; //检测客户端连接的文件描述符是否关闭
    switch (trigMode)
    {
    case 0: //水平触发
        break;
    case 1: //连接边沿触发
        connEvent_ |= EPOLLET;
        break;
    case 2: //监听边沿触发
        listenEvent_ |= EPOLLET;
        break;
    case 3: //都边沿
        listenEvent_ |= EPOLLET;
        connEvent_ |= EPOLLET;
        break;
    default: 
        listenEvent_ |= EPOLLET;
        connEvent_ |= EPOLLET;
        break;
    }
    HttpConn::isET = (connEvent_ & EPOLLET); //设置是否为边沿触发
}
//启动服务器
void WebServer::Start() {
    int timeMS = -1;   
    if(!isClose_) { LOG_INFO("-------->>> Server start! <<<--------"); }
    while(!isClose_) { //服务器不关闭就一直在epoll中检测有没有就绪的文件描述符
        if(timeoutMS_ > 0) {
            timeMS = timer_->GetNextTick(); //获取下一次超时时间
        }
        int eventCnt = epoller_->Wait(timeMS); //eventCnt：检测到就绪事件数，将事件存储到events数组，阻塞到下一次超时时间
        for(int i = 0; i < eventCnt; i++) {
            // 处理事件 
            int fd = epoller_->GetEventFd(i);
            uint32_t events = epoller_->GetEvents(i);
            if(fd == listenFd_) { //如果是监听文件描述符
                DealListen_(); //处理监听操作，接受客户端连接
            }
            else if(events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) { //如果客户端连接关闭或者出错
                assert(users_.count(fd) > 0); //无用户直接终止程序
                CloseConn_(&users_[fd]); //关闭连接
            }
            else if(events & EPOLLIN) { //客户端发来消息，可读事件
                assert(users_.count(fd) > 0);
                DealRead_(&users_[fd]); //进行读操作（子线程）
            }
            else if(events & EPOLLOUT) { //客户端可以接收数据，可写事件
                assert(users_.count(fd) > 0);
                DealWrite_(&users_[fd]); //进行写操作（子线程）
            } else {
                LOG_ERROR("Unexpected event");
            }
        }
    }
}

void WebServer::SendError_(int fd, const char*info) {
    assert(fd > 0);
    int ret = send(fd, info, strlen(info), 0);
    if(ret < 0) {
        LOG_WARN("send error to client[%d] error!", fd);
    }
    close(fd);
}

void WebServer::CloseConn_(HttpConn* client) { //关闭连接
    assert(client);
    LOG_INFO("Client[%d] quit!", client->GetFd());
    epoller_->DelFd(client->GetFd()); //epoll中删除客户端
    client->Close(); //关闭客户端
}

void WebServer::AddClient_(int fd, sockaddr_in addr) { //新增客户端连接
    assert(fd > 0);
    users_[fd].init(fd, addr); //初始化连接信息
    if(timeoutMS_ > 0) {
        timer_->add(fd, timeoutMS_, std::bind(&WebServer::CloseConn_, this, &users_[fd])); //文件描述符，超时时间，超时调用的函数。添加到定时器
    }
    epoller_->AddFd(fd, EPOLLIN | connEvent_); //添加到epoll中，检测客户端是否发来信息
    SetFdNonblock(fd);
    LOG_INFO("Client[%d] in!", users_[fd].GetFd());
}

void WebServer::DealListen_() { //接受客户端连接，初始化，更新相关信息
    struct sockaddr_in addr; //用于保存客户端信息（IP,端口）
    socklen_t len = sizeof(addr);
    do {
        int fd = accept(listenFd_, (struct sockaddr *)&addr, &len); //接受连接，fd已是客户端文件描述符
        if(fd <= 0) { return;}
        else if(HttpConn::userCount >= MAX_FD) {
            SendError_(fd, "Server busy!"); //给客户端发送错误信息
            LOG_WARN("Clients is full!");
            return;
        }
        AddClient_(fd, addr); //添加客户端，初始化新连接的客户端并添加到epool中
    } while(listenEvent_ & EPOLLET); //如果是边沿触发，需要循环处理（可能有多个客户端连接），但至少有一个客户端连接因此do-whlie
}

void WebServer::DealRead_(HttpConn* client) {
    assert(client);
    ExtentTime_(client); //增加超时时间
    threadpool_->AddTask(std::bind(&WebServer::OnRead_, this, client)); //线程池中子线程负责，将OnRead添加到线程池的任务队列中，非静态成员函数则需要传递this指针作为第一个参数
}

void WebServer::DealWrite_(HttpConn* client) {
    assert(client);
    ExtentTime_(client);
    threadpool_->AddTask(std::bind(&WebServer::OnWrite_, this, client));
}

void WebServer::ExtentTime_(HttpConn* client) {
    assert(client);
    if(timeoutMS_ > 0) { timer_->adjust(client->GetFd(), timeoutMS_); } //调整超时时间
}
//该函数在子线程执行
void WebServer::OnRead_(HttpConn* client) {
    assert(client);
    int ret = -1;
    int readErrno = 0;
    ret = client->read(&readErrno); //读数据，传出错误号
    if(ret <= 0 && readErrno != EAGAIN) { //读出错，且错误号不是重读
        CloseConn_(client);
        return;
    }
    //读成功了，请求数据存放在readBuff_中
    OnProcess(client); //对数据进行处理解析，业务逻辑
}

void WebServer::OnProcess(HttpConn* client) {
    if(client->process()) { //是否成功处理请求/响应数据并解析生成响应信息
        epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLOUT); //成功，增加是否可写事件的检测
    } else {
        epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLIN); //失败，继续检测读
    }
}

void WebServer::OnWrite_(HttpConn* client) {
    assert(client);
    int ret = -1;
    int writeErrno = 0;
    ret = client->write(&writeErrno); //写数据
    if(client->ToWriteBytes() == 0) {
        // 传输完成 
        if(client->IsKeepAlive()) {
            OnProcess(client);
            return;
        }
    }
    else if(ret < 0) {
        if(writeErrno == EAGAIN) {
            // 继续传输 
            epoller_->ModFd(client->GetFd(), connEvent_ | EPOLLOUT);
            return;
        }
    }
    CloseConn_(client);
}

//创建监听文件描述符
bool WebServer::InitSocket_() { //初始化socket
    int ret;
    struct sockaddr_in addr;
    if(port_ > 65535 || port_ < 1024) { //检查端口号
        LOG_ERROR("Port:%d error!",  port_);
        return false;
    }
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY也可以不用转换 
    addr.sin_port = htons(port_);
    struct linger optLinger = { 0 };
    if(openLinger_) {
        optLinger.l_onoff = 1;
        optLinger.l_linger = 1;
    }
    //获取监听的文件描述符
    listenFd_ = socket(AF_INET, SOCK_STREAM, 0);
    if(listenFd_ < 0) {
        LOG_ERROR("Create listen socket error!", port_);
        return false;
    }

    ret = setsockopt(listenFd_, SOL_SOCKET, SO_LINGER, &optLinger, sizeof(optLinger));
    if(ret < 0) {
        close(listenFd_);
        LOG_ERROR("Init linger error!", port_);
        return false;
    }
    // 端口复用 只有最后一个套接字会正常接收数据。
    int optval = 1; //0不复用，1复用
    ret = setsockopt(listenFd_, SOL_SOCKET, SO_REUSEADDR, (const void*)&optval, sizeof(int));
    if(ret == -1) {
        LOG_ERROR("set socket setsockopt error !");
        close(listenFd_);
        return false;
    }

    ret = bind(listenFd_, (struct sockaddr *)&addr, sizeof(addr));
    if(ret < 0) {
        LOG_ERROR("Bind port:%d error!", port_);
        close(listenFd_);
        return false;
    }

    ret = listen(listenFd_, 6);
    if(ret < 0) {
        LOG_ERROR("Listen port:%d error!", port_);
        close(listenFd_);
        return false;
    }
    //在epoll中监听文件描述符的检测事件中加入检测是否有数据写入，可读
    ret = epoller_->AddFd(listenFd_,  listenEvent_ | EPOLLIN); 
    if(ret == 0) {
        LOG_ERROR("Add listenFd error!");
        close(listenFd_);
        return false;
    }
    SetFdNonblock(listenFd_);
    LOG_INFO("Server port:%d", port_);
    return true;
}
//设置文件描述符非阻塞
int WebServer::SetFdNonblock(int fd) {
    assert(fd > 0);
    return fcntl(fd, F_SETFL, fcntl(fd, F_GETFD, 0) | O_NONBLOCK); 
}


